package model;

import java.util.List;


public class IncrementalCostCalculator {
//	private static final EtmMonitor etmMonitor = EtmManager.getEtmMonitor();

//	private Problem problem;

	private SolverEnvironment env;

	long initial_load_cost;
	long initial_balance_cost;
		
	long[] costComponentMemory = new long[5]; 
	
	//The current solution cost components
	long new_load_cost;
	long new_balance_cost;
	long process_move_cost;
	long machine_move_cost;
	long service_move_cost;
	
	//The last move cost components
	long new_load_cost_delta_temp;
	long new_balance_cost_delta_temp;
	long process_move_cost_delta_temp;
	long machine_move_cost_delta_temp;
	long service_move_cost_delta_temp;

	// Size problem.S
	long[] movedServiceProcesses;
	int maxMovedService = 0;

	public IncrementalCostCalculator() {
		
	}

//	public void setBaseSolution(Solution initial_solution) {
//		env.setInitialSolution(initial_solution);
//		initial_load_cost = getInitialSolutionLoadCost();
//		initial_balance_cost = getInitialSolutionBalanceCost();
//	}

//	public void setCurrentSolution(Solution current_solution) {
//		env.setCurrentSolution(current_solution);
//		computeCost();
//	}

	private long getLoadCost(Solution aSolution) {
//		EtmPoint point = etmMonitor
//				.createPoint("EnhancedCostEvaluator:getLoadCost");
		long sum = 0;
		for (int r = 0; r < env.getProblem().RE; r++) {
			long sum2 = 0;
			for (int m = 0; m < env.getProblem().M; m++) {
				int usage = aSolution.getUsageOfMachineForResource(m, r);
				int safety_capacity = env.getProblem().SC[m][r];
				int delta = usage - safety_capacity;
				int max;
				if (delta > 0)
					max = delta;
				else
					max = 0;
				// is_load_cost_MxR[m][r] = max;
				sum2 += max * env.getProblem().getResourceByIndex(r).getWeightLoadCost();
				// System.out.printf("[ECE] load cost m%dr%d = %d\n", m, r,
				// max);
			}
			sum += sum2;
		}
		// System.out.println();
//		point.collect();
		return sum;
	}

	private long getInitialSolutionLoadCost() {
		return getLoadCost(env.getInitialSolution());
	}

	private long getCurrentSolutionLoadCost() {
		return getLoadCost(env.getPotentialSolution());
	}

	protected long getBalanceCost(Solution aSolution) {
//		EtmPoint point = etmMonitor
//				.createPoint("EnhancedCostEvaluator:getBalanceCost");
		long sum = 0;
		// int bo_index = 0;
		for (BalanceObjective bo : env.getProblem().getBalanceObjectives()) {
			for (int m = 0; m < env.getProblem().M; m++) {
				int amr1 = env.getProblem().C[m][bo.resourcea]
						- aSolution.getUsageOfMachineForResource(m,
								bo.resourcea);
				int amr2 = env.getProblem().C[m][bo.resourceb]
						- aSolution.getUsageOfMachineForResource(m,
								bo.resourceb);
				int delta = bo.target * amr1 - amr2;
				int max;
				if (delta > 0) {
					max = delta;
				} else {
					max = 0;
				}
				// System.out.printf("[ECE] balance cost bo%d_m%d = %d\n",
				// bo_index, m, max);
				sum += max * bo.weightBalanceCost;
			}
//			System.out.println();
			// bo_index++;
		}
//		point.collect();
		return sum;
	}

	public long getInitialSolutionBalanceCost() {
		return getBalanceCost(env.getInitialSolution());
	}

	public long getCurrentSolutionBalanceCost() {
		return getBalanceCost(env.getPotentialSolution());
	}

	private long getProcessMoveCost() {
////		EtmPoint point = etmMonitor
//				.createPoint("EnhancedCostEvaluator:getProcessMoveCost");
		long sum = 0;
		for (int p = 0; p < env.getProblem().P; p++) {
			if (env.getInitialSolution().getMachineIdForProcessId(p) != env.getPotentialSolution()
					.getMachineIdForProcessId(p)) {
				Process process = env.getProblem().getProcesses().get(p);
				sum += process.getProcessMoveCost();
			}
		}
//		point.collect();
		return sum * env.getProblem().getWeightProcessMoveCost();
	}

	private long getServiceMoveCost() {
//		EtmPoint point = etmMonitor
//				.createPoint("EnhancedCostEvaluator:getServiceMoveCost");
		long max=0;
		for (int s = 0; s < env.getProblem().S; s++) {
			Service aService = env.getProblem().getServices().get(s);
			long c = 0;
			for (Integer p : aService.getProcesses()) {
				if (env.getInitialSolution().getMachineIdForProcessId(p) != env.getPotentialSolution()
						.getMachineIdForProcessId(p)) {
					c++;
				}
			}
			movedServiceProcesses[s] = c;
			if (c > max){
				max = c;
				maxMovedService = s;
			}
		}
//		point.collect();
		assert(max==movedServiceProcesses[maxMovedService]);
		return movedServiceProcesses[maxMovedService]
				* env.getProblem().getWeightServiceMoveCost();
	}

	private long getMachineMoveCost() {
//		EtmPoint point = etmMonitor
//				.createPoint("EnhancedCostEvaluator:getMachineMoveCost");
		long sum = 0;
		for (int p = 0; p < env.getProblem().P; p++) {
			int initial_machine_id = env.getInitialSolution()
					.getMachineIdForProcessId(p);
			Machine aMachine = env.getProblem().getMachines().get(initial_machine_id);
			int new_machine_id = env.getPotentialSolution().getMachineIdForProcessId(p);
			sum += aMachine.getMachineMoveCost(new_machine_id);
		}
//		point.collect();
		return sum * env.getProblem().getWeightMachineMoveCost();
	}

	// before each call
	public void computeCost() {
		new_load_cost = getCurrentSolutionLoadCost();
		new_balance_cost = getCurrentSolutionBalanceCost();
		machine_move_cost = getMachineMoveCost();
		service_move_cost = getServiceMoveCost();
		process_move_cost = getProcessMoveCost();
	}

	public long getInitialSolutionCost() {
		return initial_load_cost + initial_balance_cost;
	}

	public long getNewSolutionCost() {
		// System.out.printf("LC=%d BC=%d PMC=%d SMC=%d MMC=%d\n",
		// new_load_cost,
		// new_balance_cost, process_move_cost, service_move_cost,
		// machine_move_cost);
		return new_load_cost + new_balance_cost + process_move_cost
				+ service_move_cost + machine_move_cost;
	}
	
	public long getPotentialSolutionCost() {
		return getNewSolutionCost() +  new_load_cost_delta_temp + new_balance_cost_delta_temp + machine_move_cost_delta_temp
		+ service_move_cost_delta_temp + process_move_cost_delta_temp;
	}

	public long getInitialProcessMoveToMachineCost(int p_id, int new_m_id) {
		return getInitialSolutionCost()
				+ getProcessMoveToMachineCost(env.getInitialSolution(), p_id, new_m_id);
	}

	public long getCurrentProcessMoveToMachineCost(int p_id, int new_m_id) {
		return getNewSolutionCost()
				+ getProcessMoveToMachineCost(env.getPotentialSolution(), p_id, new_m_id);
	}

	public long getProcessMoveToMachineCost(Solution baseSolution, int p_id,
			int new_m_id) {
		int orig_m_id = env.getPotentialSolution().getMachineIdForProcessId(p_id);
		return getProcessMoveToMachineCost(baseSolution,p_id,orig_m_id, new_m_id);
	}
	
	// Returns the delta cost of moving a process from a base solution
	public long getProcessMoveToMachineCost(Solution baseSolution, int p_id, 
			int orig_m_id, int new_m_id) {
		if (orig_m_id == new_m_id)
			return 0;
		// Calculate delta load cost
		long remove_load_cost = getUnLoadGain(baseSolution, p_id, orig_m_id);
		long add_load_cost = getLoadCost(baseSolution, p_id, new_m_id);
		new_load_cost_delta_temp = add_load_cost - remove_load_cost;

		// Calculate delta balance cost
		long remove_balance_cost = getBalanceRemoveGain(baseSolution, p_id,
				orig_m_id);
		long add_balance_cost = getBalanceAddCost(baseSolution, p_id, new_m_id);
		new_balance_cost_delta_temp = add_balance_cost + remove_balance_cost;
		
		int ini_m_id = env.getInitialSolution().getMachineIdForProcessId(p_id);
		machine_move_cost_delta_temp = getMachineMoveCost(ini_m_id, orig_m_id,
				new_m_id);
		service_move_cost_delta_temp = getServiceMoveCost(p_id, orig_m_id, new_m_id);
		Process aProcess = env.getProblem().getProcesses().get(p_id);
		process_move_cost_delta_temp = getProcessMoveCost(
				aProcess.getProcessMoveCost(), ini_m_id, orig_m_id, new_m_id);
		// System.out.printf("DLC=%d DBC=%d DPMC=%d DSMC=%d DMMC=%d\n",
		// delta_load, delta_balance, process_move_cost,
		// service_move_cost, machine_move_cost);
		return new_load_cost_delta_temp + new_balance_cost_delta_temp + machine_move_cost_delta_temp
				+ service_move_cost_delta_temp + process_move_cost_delta_temp;
	}

	protected long getUnLoadGain(Solution baseSolution, int pId, int origMId) {
		assert (baseSolution.getMachineIdForProcessId(pId) == origMId);
		// load cost
		long unLoadGain = 0;
		for (int r = 0; r < env.getProblem().RE; r++) {
			int usage = baseSolution.getUsageOfMachineForResource(origMId, r);
			int safety_capacity = env.getProblem().SC[origMId][r];
			int delta = usage - safety_capacity;
			int max;
			if (delta > 0)
				max = delta;
			else
				max = 0;
			max = Math.min(max, env.getProblem().R[pId][r]);
			unLoadGain += max
					* env.getProblem().getResourceByIndex(r).getWeightLoadCost();
		}
		return unLoadGain;
	}

	protected long getLoadCost(Solution baseSolution, int pId, int newMId) {
		long loadCost = 0;
		for (int r = 0; r < env.getProblem().RE; r++) {
			int usage = baseSolution.getUsageOfMachineForResource(newMId, r);
			int safety_capacity = env.getProblem().SC[newMId][r];
			int delta = usage - safety_capacity;
			int max;

			if (delta >= 0)
				max = env.getProblem().R[pId][r];
			else
				max = Math.max(env.getProblem().R[pId][r] + delta, 0);

			loadCost += max * env.getProblem().getResourceByIndex(r).getWeightLoadCost();
		}
		return loadCost;
	}

	protected long getBalanceRemoveGain(Solution baseSolution, int pId,
			int origMId) {
		assert (baseSolution.getMachineIdForProcessId(pId) == origMId);
		// balance cost
		long balanceGain = 0;
		for (BalanceObjective bo : env.getProblem().getBalanceObjectives()) {
			long amr1 = env.getProblem().C[origMId][bo.resourcea]
					- baseSolution.getUsageOfMachineForResource(origMId,
							bo.resourcea);
			long amr2 = env.getProblem().C[origMId][bo.resourceb]
					- baseSolution.getUsageOfMachineForResource(origMId,
							bo.resourceb);
			long delta = bo.target * amr1 - amr2;

			long pra = env.getProblem().getR(pId, bo.resourcea);
			long prb = env.getProblem().getR(pId, bo.resourceb);
			long amr1c = amr1 + pra;
			long amr2c = amr2 + prb;
			long deltac = bo.target * amr1c - amr2c;

			long max = 0;
			if (delta > max)
				max = delta;
			long max2 = 0;
			if (deltac > max2)
				max2 = deltac;

			balanceGain += (max2 - max) * bo.weightBalanceCost;
		}
		return balanceGain;
	}

	protected long getBalanceAddCost(Solution baseSolution, int pId, int newMId) {
		// balance cost
		long balanceCost = 0;
		for (BalanceObjective bo : env.getProblem().getBalanceObjectives()) {
			long amr1 = env.getProblem().C[newMId][bo.resourcea]
					- baseSolution.getUsageOfMachineForResource(newMId,
							bo.resourcea);
			long amr2 = env.getProblem().C[newMId][bo.resourceb]
					- baseSolution.getUsageOfMachineForResource(newMId,
							bo.resourceb);
			long delta = bo.target * amr1 - amr2;

			long pra = env.getProblem().getR(pId, bo.resourcea);
			long prb = env.getProblem().getR(pId, bo.resourceb);
			long amr1c = amr1 - pra;
			long amr2c = amr2 - prb;
			long deltac = bo.target * amr1c - amr2c;

			long max = 0;
			if (delta > max)
				max = delta;
			long max2 = 0;
			if (deltac > max2)
				max2 = deltac;
			balanceCost += (max2 - max) * bo.weightBalanceCost;
		}
		return balanceCost;
	}

	protected long getMachineMoveCost(int ini_m_id, int cur_m_id, int pot_m_id) {
		return (env.getProblem().MMC[ini_m_id][pot_m_id] - env.getProblem().MMC[ini_m_id][cur_m_id])
				* env.getProblem().getWeightMachineMoveCost();
	}

	protected long getServiceMoveCost(int pId, int origMId, int newMId) {
		int sId = env.getProblem().getServiceIdForProcessId(pId);

		// The process does not belong to a Service that has maximum moved
		// processes
		if (movedServiceProcesses[sId] < movedServiceProcesses[maxMovedService]) {
			return 0;
		}
		// our service could be responsible for the Service move cost
		// or more than one services shared the maximum value
		else {
			int initMachine = env.getInitialSolution().getMachineIdForProcessId(pId);
			boolean movedToInitNew = initMachine != newMId;
			boolean movedToInitBase = initMachine != origMId;

			if (movedToInitNew) {
				if (movedToInitBase)// The cost is already calculated
					return 0;
				else { // The process moves with an added cost
					return 1 * env.getProblem().getWeightServiceMoveCost();
				}
			} else {
				if (movedToInitBase) {// The process moves back to the original
										// machine
					// There is another service with maximum moved Processes
					if (sId != maxMovedService)
						return 0;

					// We are the maximum Service
					long max = movedServiceProcesses[maxMovedService];
					// Check if there is another Service with maxMovedProcesses
					for (int s = 0; s < env.getProblem().S; s++)
						if (movedServiceProcesses[s] >= max && s != sId) // We
																			// are
																			// not
																			// the
																			// only
																			// max
																			// service
							return 0;
					// We are the only max service
					return -1 * env.getProblem().getWeightServiceMoveCost();
				} else { // This can not happen if origMId != newMId
					return 0;
				}
			}
		}
	}

	protected long getProcessMoveCost(int process_move_cost, int ini_m_id,
			int cur_m_id, int pot_m_id) {
		if (ini_m_id == pot_m_id) {
			return -process_move_cost * env.getProblem().getWeightProcessMoveCost();
		} else if (ini_m_id == cur_m_id) {
			return process_move_cost * env.getProblem().getWeightProcessMoveCost();
		} else
			return 0;
	}
	
	public void updateNewSolutionCost(){
		new_load_cost+=new_load_cost_delta_temp;
		new_balance_cost+=new_balance_cost_delta_temp;
		process_move_cost+=process_move_cost_delta_temp;
		machine_move_cost+=machine_move_cost_delta_temp;
		service_move_cost+=service_move_cost_delta_temp;
		clearDelta();
	}
	
	public void printCostAnalysis() {
		printInitialSolutionCostAnalysis();
		printNewSolutionCostAnalysis("New Solution");
	}

	public void printInitialSolutionCostAnalysis() {
		StringBuilder sb = new StringBuilder();
		sb.append(String.format(
				"[%sSolution=%,d] load cost=%,d balance cost=%,d",
				(env.getPotentialSolution() == null) ? "" : "INI ", getInitialSolutionCost(),
				initial_load_cost, initial_balance_cost));
		sb.append("\n");
		System.out.println(sb.toString());
	}

	public void printNewSolutionCostAnalysis(String s) {
		StringBuilder sb = new StringBuilder();
		sb.append(String
				.format("[%s=%,d]\tload cost=%,d balance cost=%,d process move cost=%,d service move cost=%,d machine move cost=%,d\n",
						s, getNewSolutionCost(), new_load_cost,
						new_balance_cost, process_move_cost, service_move_cost,
						machine_move_cost));
		sb.append(String
				.format("\t\tdelta_load cost=%,d delta_balance cost=%,d delta_process move cost=%,d delta_service move cost=%,d delta_machine move cost=%,d\n",
						new_load_cost_delta_temp, new_balance_cost_delta_temp, process_move_cost_delta_temp,
						service_move_cost_delta_temp,machine_move_cost_delta_temp));
		sb.append(String
				.format("\t\ttotal_load cost=%,d total_balance cost=%,d total_process move cost=%,d total_service move cost=%,d total_machine move cost=%,d\n",
						new_load_cost+new_load_cost_delta_temp, 
						new_balance_cost+new_balance_cost_delta_temp, 
						process_move_cost+process_move_cost_delta_temp,
						service_move_cost+service_move_cost_delta_temp,
						machine_move_cost+machine_move_cost_delta_temp));		
		sb.append("\n");
		System.out.println(sb.toString());
	}
	
	public void initServiceMove(){
		int maxMovedService = 0;
		for(int s=0;s<env.getProblem().S;s++){
			List<Integer> sp = env.getProblem().getProcessesIdsForServiceId(s);
			for(Integer pId : sp){
				if (env.getInitialSolution().getMachineIdForProcessId(pId)!=
						env.getPotentialSolution().getMachineIdForProcessId(pId))
					movedServiceProcesses[s]++;
			}
			if(movedServiceProcesses[s]>movedServiceProcesses[maxMovedService])
				maxMovedService = s;
		}
	}
	
	
	public void updateServiceMove(int pId, int origMId, int new_MId){
		
		if (env.getInitialSolution().getMachineIdForProcessId(pId) != origMId &&
				env.getInitialSolution().getMachineIdForProcessId(pId) == new_MId ){
			int sId = env.getProblem().getServiceIdForProcessId(pId);
			movedServiceProcesses[sId]--;
			if(sId==maxMovedService){ //If pId belonged to the most moved service
				for(int s=0;s<env.getProblem().S;s++){
					if(movedServiceProcesses[s]>movedServiceProcesses[maxMovedService])
						maxMovedService=s;
				}
			}
		}else if (env.getInitialSolution().getMachineIdForProcessId(pId) == origMId &&
				env.getInitialSolution().getMachineIdForProcessId(pId) != new_MId ){
			int sId = env.getProblem().getServiceIdForProcessId(pId);
			movedServiceProcesses[sId]++;
			if(movedServiceProcesses[sId]>movedServiceProcesses[maxMovedService]){ //If pId belonged to the most moved service
				maxMovedService=sId;
			}
		}			
	}
	
	public void saveCostComponentsState(){
		costComponentMemory[0]= new_load_cost;
		costComponentMemory[1]= new_balance_cost;
		costComponentMemory[2]= process_move_cost;
		costComponentMemory[3]= machine_move_cost;
		costComponentMemory[4]= service_move_cost;
	}
	
	public void restoreCostComponentsState(){
		new_load_cost= costComponentMemory[0];
		new_balance_cost=costComponentMemory[1];
		process_move_cost=costComponentMemory[2] ;
		machine_move_cost=costComponentMemory[3];
		service_move_cost=costComponentMemory[4];
	}

	public long[] getCostComponentMemory() {
		return costComponentMemory;
	}

	public void setCostComponentMemory(long[] costComponentMemory) {
		this.costComponentMemory = costComponentMemory;
	}
	
	public long[] saveAndGetCostComponentMemory() {
		saveCostComponentsState();
		return costComponentMemory.clone();
	}

	public void setAndRestoreCostComponentMemory(long[] costComponentMemory) {
		this.costComponentMemory = costComponentMemory;
		restoreCostComponentsState();
	}
	
	public long getProcessRemoveProfit(Solution baseSolution, int p_id){
		int orig_m_id = baseSolution.getMachineIdForProcessId(p_id);
		long remove_load_gain = getUnLoadGain(baseSolution, p_id, orig_m_id);
		long remove_balance_gain = getBalanceRemoveGain(baseSolution, p_id,
				orig_m_id);
				
		//PA Can we calculate these costs if we do not have a move operation?????????
//		int ini_m_id = initial_solution.getMachineIdForProcessId(p_id);
//		machine_move_cost_delta_temp = getMachineMoveCost(ini_m_id, orig_m_id,
//				new_m_id);
//		service_move_cost_delta_temp = getServiceMoveCost(p_id, orig_m_id, new_m_id);
//		Process aProcess = problem.getProcesses().get(p_id);
//		process_move_cost_delta_temp = getProcessMoveCost(
//				aProcess.getProcessMoveCost(), ini_m_id, orig_m_id, new_m_id);
		// System.out.printf("DLC=%d DBC=%d DPMC=%d DSMC=%d DMMC=%d\n",
		// delta_load, delta_balance, process_move_cost,
		// service_move_cost, machine_move_cost);
		return remove_load_gain + remove_balance_gain;
	}
	
	// Returns the delta cost of moving a process from a base solution
	public long getProcessExcahngeProfit(Solution baseSolution, int p_id1, int p_id2){
		int orig_m_id1 = baseSolution.getMachineIdForProcessId(p_id1);
		int orig_m_id2 = baseSolution.getMachineIdForProcessId(p_id2);
		return getProcessExcahngeProfit(baseSolution, p_id1, p_id2, orig_m_id1, orig_m_id2);
	}
	
	// Returns the delta cost of moving a process from a base solution
	public long getProcessExcahngeProfit(Solution baseSolution, int p_id1, int p_id2, 
			int orig_m_id1, int orig_m_id2) {
		if (orig_m_id1 == orig_m_id2 || p_id1==p_id2)
			return 0;
		// Calculate delta load cost
		long remove_load_cost1 = getUnLoadGain(baseSolution, p_id1, orig_m_id1);
		long add_load_cost1 = getLoadCost(baseSolution, p_id1, orig_m_id2, p_id2);
		long remove_load_cost2 = getUnLoadGain(baseSolution, p_id2, orig_m_id2);
		long add_load_cost2 = getLoadCost(baseSolution, p_id2, orig_m_id1, p_id1);
		new_load_cost_delta_temp = add_load_cost1 - remove_load_cost1 +
								   add_load_cost2 - remove_load_cost2;

		// Calculate delta balance cost
		long remove_balance_cost1 = getBalanceRemoveGain(baseSolution, p_id1,
				orig_m_id1);
		long add_balance_cost1 = -getBalanceAddCost(baseSolution, p_id1, orig_m_id2, p_id2);
		long remove_balance_cost2 = getBalanceRemoveGain(baseSolution, p_id2,
				orig_m_id2);
		long add_balance_cost2 = -getBalanceAddCost(baseSolution, p_id2, orig_m_id1, p_id1);
		new_balance_cost_delta_temp = add_balance_cost1 + remove_balance_cost1+
									  add_balance_cost2 + remove_balance_cost2;
		
		int ini_m_id1 = env.getInitialSolution().getMachineIdForProcessId(p_id1);
		int ini_m_id2 = env.getInitialSolution().getMachineIdForProcessId(p_id2);
		machine_move_cost_delta_temp = getMachineMoveCost(ini_m_id1, orig_m_id1,
				orig_m_id2) + getMachineMoveCost(ini_m_id2, orig_m_id2,
						orig_m_id1);
		
		service_move_cost_delta_temp = getServiceExchangeCost(p_id1, orig_m_id1, p_id2, orig_m_id2);
		
		
		Process aProcess1 = env.getProblem().getProcesses().get(p_id1);
		Process aProcess2 = env.getProblem().getProcesses().get(p_id2);
		process_move_cost_delta_temp = 
			getProcessMoveCost(aProcess1.getProcessMoveCost(), ini_m_id1, orig_m_id1, orig_m_id2) +
			getProcessMoveCost(aProcess2.getProcessMoveCost(), ini_m_id2, orig_m_id2, orig_m_id1);
		
		// System.out.printf("DLC=%d DBC=%d DPMC=%d DSMC=%d DMMC=%d\n",
		// delta_load, delta_balance, process_move_cost,
		// service_move_cost, machine_move_cost);
		return new_load_cost_delta_temp + new_balance_cost_delta_temp + machine_move_cost_delta_temp
				+ service_move_cost_delta_temp + process_move_cost_delta_temp;
	}

	private long getServiceExchangeCost(int pId1, int origMId1, int pId2,
			int origMId2) {
		int sId1 = env.getProblem().getServiceIdForProcessId(pId1);
		int sId2 = env.getProblem().getServiceIdForProcessId(pId2);
		boolean isSameService = sId1==sId2; 

		// The process does not belong to a Service that has maximum moved
		// processes
		if (!isSameService){
			if(movedServiceProcesses[sId1] < movedServiceProcesses[sId2]){
				long ret2 = getServiceMoveCost(pId2, origMId2, origMId1);
				if(ret2<0 && ((movedServiceProcesses[sId1]+1)==movedServiceProcesses[sId2])
						&& movedServiceProcesses[sId2] == movedServiceProcesses[maxMovedService]){
						int initMachine1 = env.getInitialSolution().getMachineIdForProcessId(pId1);
						boolean movedToInitNew1 = initMachine1 == origMId2;
						boolean movedFromInitBase1 = initMachine1 == origMId1;
						if(!movedToInitNew1 && movedFromInitBase1 &&
								((movedServiceProcesses[sId1]+1)==movedServiceProcesses[maxMovedService]))
							return 0;										
				}
				return ret2;
			}
			else if (movedServiceProcesses[sId2] < movedServiceProcesses[sId1]){
				long ret1 =  getServiceMoveCost(pId1, origMId1, origMId2);
				if(ret1<0 && ((movedServiceProcesses[sId2]+1)==movedServiceProcesses[sId1])
					&& movedServiceProcesses[sId1] == movedServiceProcesses[maxMovedService]){
					int initMachine2 = env.getInitialSolution().getMachineIdForProcessId(pId2);
					boolean movedToInitNew2 = initMachine2 == origMId1;
					boolean movedFromInitBase2 = initMachine2 == origMId2;
					if(!movedToInitNew2 && movedFromInitBase2 &&
							((movedServiceProcesses[sId2]+1)==movedServiceProcesses[maxMovedService]))
						return 0;										
				}
				return ret1;
			}
			else{
				if(movedServiceProcesses[sId1] < movedServiceProcesses[maxMovedService])
					return 0;
				else{	
					int initMachine1 = env.getInitialSolution().getMachineIdForProcessId(pId1);
					int initMachine2 = env.getInitialSolution().getMachineIdForProcessId(pId2);
					boolean movedToInitNew1 = initMachine1 == origMId2;
					boolean movedFromInitBase1 = initMachine1 == origMId1;
					boolean movedToInitNew2 = initMachine2 == origMId1;
					boolean movedFromInitBase2 = initMachine2 == origMId2;
					int sId1Change=0,sId2Change=0;
					if (!movedToInitNew1) {
						if (movedFromInitBase1){
							// The process moves from the initial with an added cost
							sId1Change+= 1 ;
						}
					}
					else {
						if (!movedFromInitBase1){
							// The process moves to the initial with reduced cost
							sId1Change+= -1 ;
						}
					}
					if (!movedToInitNew2) {
						if (movedFromInitBase2){
							// The process moves from the initial with an added cost
							sId2Change+= 1 ;
						}
					}
					else {
						if (!movedFromInitBase2){
							// The process moves to the initial with reduced cost
							sId2Change+= -1 ;
						}
					}
															
					if(sId1==maxMovedService){
						if(sId1Change>0){
							return sId1Change*env.getProblem().getWeightServiceMoveCost();
						}else if (sId1Change==0){
							if(sId2Change>=0){
								return sId2Change*env.getProblem().getWeightServiceMoveCost();
							}
							else
								return 0;
						}else{
							if(sId2Change>=0){
								return sId2Change*env.getProblem().getWeightServiceMoveCost();
							}
							else{
								long max = movedServiceProcesses[maxMovedService];
								// Check if there is another Service with maxMovedProcesses
								for (int s = 0; s < env.getProblem().S; s++)
									if (movedServiceProcesses[s] >= max && s != sId1 && s != sId2)
										return 0;
								return sId1Change*env.getProblem().getWeightServiceMoveCost();
							}
						}
					}else if(sId2==maxMovedService){
						if(sId2Change>0){
							return sId2Change*env.getProblem().getWeightServiceMoveCost();
						}else if (sId2Change==0){
							if(sId1Change>=0){
								return sId1Change*env.getProblem().getWeightServiceMoveCost();
							}
							else
								return 0;
						}else{
							if(sId1Change>=0){
								return sId1Change*env.getProblem().getWeightServiceMoveCost();
							}
							else{
								long max = movedServiceProcesses[maxMovedService];
								// Check if there is another Service with maxMovedProcesses
								for (int s = 0; s < env.getProblem().S; s++)
									if (movedServiceProcesses[s] >= max && s != sId1 && s != sId2)
										return 0;
								return sId2Change*env.getProblem().getWeightServiceMoveCost();
							}
						}					
					}
					else{ //Non of them is the maxService
						if(sId1Change>0){
							return sId1Change*env.getProblem().getWeightServiceMoveCost();
						}else if(sId2Change>0){
							return sId2Change*env.getProblem().getWeightServiceMoveCost();
						}
						else
							return 0;
					}
				}
			}
		}
		else {
			//Both processes belong to the same service				
			// our service could be responsible for the Service move cost
			// or more than one services shared the maximum value
			
			int initMachine1 = env.getInitialSolution().getMachineIdForProcessId(pId1);
			int initMachine2 = env.getInitialSolution().getMachineIdForProcessId(pId2);
			boolean movedToInitNew1 = initMachine1 == origMId2;
			boolean movedFromInitBase1 = initMachine1 == origMId1;
			boolean movedToInitNew2 = initMachine2 == origMId1;
			boolean movedFromInitBase2 = initMachine2 == origMId2;
			
			int countChange=0;
			if (!movedToInitNew1) {
				if (movedFromInitBase1){
					// The process moves from the initial with an added cost
					countChange+= 1 ;
				}
			}
			else {
				if (!movedFromInitBase1){
					// The process moves to the initial with reduced cost
					countChange+= -1 ;
				}
			}
			
			if (!movedToInitNew2) {
				if (movedFromInitBase2){
					// The process moves from the initial with an added cost
					countChange+= 1 ;
				}
			}
			else {
				if (!movedFromInitBase2){
					// The process moves to the initial with reduced cost
					countChange+= -1 ;
				}
			}
			
			if (sId1 != maxMovedService){
				// There is another service with maximum moved Processes
				if (countChange<=0 )
					return 0;
				if(movedServiceProcesses[sId1]+countChange>movedServiceProcesses[maxMovedService]){
					//We are now the maximum Service
					return (movedServiceProcesses[sId1]+countChange
							-movedServiceProcesses[maxMovedService])*env.getProblem().getWeightServiceMoveCost();
				}
				else
					return 0;
			}
			else{
				// We are the maximum Service
				long oldmax = movedServiceProcesses[maxMovedService];
				if(countChange>=0)
					return countChange*env.getProblem().getWeightServiceMoveCost();
				else{
					movedServiceProcesses[maxMovedService]+=countChange;
					long max=movedServiceProcesses[maxMovedService];
					// Check if there is another Service with maxMovedProcesses
					for (int s = 0; s < env.getProblem().S; s++){
						if (movedServiceProcesses[s] >= max){ 
							// We are not the only max service
							max=movedServiceProcesses[s];
						}
					}
					movedServiceProcesses[maxMovedService]-=countChange;
					// We are the only max service
					return (max-oldmax) * env.getProblem().getWeightServiceMoveCost();
				}
			}
		}
	}

	private long getLoadCost(Solution baseSolution, int pId1, int origMId2,
			int pId2) {
		long loadCost = 0;
		for (int r = 0; r < env.getProblem().RE; r++) {
			int usage = baseSolution.getUsageOfMachineForResource(origMId2, r) - env.getProblem().R[pId2][r];
			int safety_capacity = env.getProblem().SC[origMId2][r];
			int delta = usage - safety_capacity;
			int max;

			if (delta >= 0)
				max = env.getProblem().R[pId1][r];
			else
				max = Math.max(env.getProblem().R[pId1][r] + delta, 0);

			loadCost += max * env.getProblem().getResourceByIndex(r).getWeightLoadCost();
		}
		return loadCost;
	}
	
	protected long getBalanceAddCost(Solution baseSolution, int pId1,
			int origMId2, int pId2) {
		// balance cost
		long balanceCost = 0;
		for (BalanceObjective bo : env.getProblem().getBalanceObjectives()) {
			long p2ra = env.getProblem().getR(pId2, bo.resourcea);
			long p2rb = env.getProblem().getR(pId2, bo.resourceb);
			long amr1 = env.getProblem().C[origMId2][bo.resourcea]
					- (baseSolution.getUsageOfMachineForResource(origMId2,
							bo.resourcea)-p2ra);
			long amr2 = env.getProblem().C[origMId2][bo.resourceb]
					- (baseSolution.getUsageOfMachineForResource(origMId2,
							bo.resourceb)-p2rb);
			long delta = bo.target * amr1 - amr2;

			long p1ra = env.getProblem().getR(pId1, bo.resourcea);
			long p1rb = env.getProblem().getR(pId1, bo.resourceb);
			long amr1c = amr1 - p1ra;
			long amr2c = amr2 - p1rb;
			long deltac = bo.target * amr1c - amr2c;

			long max = 0;
			if (delta > max)
				max = delta;
			long max2 = 0;
			if (deltac > max2)
				max2 = deltac;
			balanceCost += (max - max2) * bo.weightBalanceCost;
		}
		return balanceCost;
	}
	
	// Returns a rough estimate of the delta cost of moving a process from a base solution
	public long getRoughProcessMoveToMachineCost(Solution baseSolution, int p_id, 
			int orig_m_id, int new_m_id) {
		if (orig_m_id == new_m_id)
			return 0;
		// Calculate delta load cost
		long remove_load_cost = getUnLoadGain(baseSolution, p_id, orig_m_id);
		long add_load_cost = getLoadCost(baseSolution, p_id, new_m_id);
		new_load_cost_delta_temp = add_load_cost - remove_load_cost;

		// Calculate delta balance cost
		long remove_balance_cost = getBalanceRemoveGain(baseSolution, p_id,
				orig_m_id);
		long add_balance_cost = getBalanceAddCost(baseSolution, p_id, new_m_id);
		new_balance_cost_delta_temp = add_balance_cost + remove_balance_cost;
		
		return new_load_cost_delta_temp + new_balance_cost_delta_temp;
	}
	
	public long getBalanceCost(Solution aSolution, int mId){
		long sum = 0;
		// int bo_index = 0;
		for (BalanceObjective bo : env.getProblem().getBalanceObjectives()) {
				int amr1 = env.getProblem().C[mId][bo.resourcea]
						- aSolution.getUsageOfMachineForResource(mId,
								bo.resourcea);
				int amr2 = env.getProblem().C[mId][bo.resourceb]
						- aSolution.getUsageOfMachineForResource(mId,
								bo.resourceb);
				int delta = bo.target * amr1 - amr2;
				int max;
				if (delta > 0) {
					max = delta;
				} else {
					max = 0;
				}
				// System.out.printf("[ECE] balance cost bo%d_m%d = %d\n",
				// bo_index, m, max);
				sum += max * bo.weightBalanceCost;
//			System.out.println();
			// bo_index++;
		}
		return sum;
	}
	
	public long getLoadCost(Solution aSolution, int mId) {
		long sum = 0;
		for (int r = 0; r < env.getProblem().RE; r++) {
				int usage = aSolution.getUsageOfMachineForResource(mId, r);
				int safety_capacity = env.getProblem().SC[mId][r];
				int delta = usage - safety_capacity;
				int max;
				if (delta > 0)
					max = delta;
				else
					max = 0;
				// is_load_cost_MxR[m][r] = max;
				sum += max * env.getProblem().getResourceByIndex(r).getWeightLoadCost();
		}
		return sum;
	}

	public SolverEnvironment getEnv() {
		return env;
	}

	public void setEnv(SolverEnvironment env) {
		this.env = env;
		init();
	}
	
	void init(){
		movedServiceProcesses = new long[env.getProblem().S];
		updateStructuresOnInitialSolutionChange();
		updateStructuresOnSolutionChange();
	}
	
	public void updateStructuresOnInitialSolutionChange(){	
		initial_load_cost = getInitialSolutionLoadCost();
		initial_balance_cost = getInitialSolutionBalanceCost();
	}

	public void updateStructuresOnSolutionChange(){	
		initServiceMove();
		computeCost();
	}

	public void clearDelta() {
		new_load_cost_delta_temp=0;
		new_balance_cost_delta_temp=0;
		process_move_cost_delta_temp=0;
		machine_move_cost_delta_temp=0;
		service_move_cost_delta_temp=0;
	}
	
	public long getMAchineMoveCost(final Solution aSolution, int ini_m_id){
		List<Integer> mp = env.getInitialSolution().getProcessesForMachine(ini_m_id);
		long sum=0;
		for(int p : mp){
			int pot_m_id = env.getPotentialSolution().getMachineIdForProcessId(p);
			if(pot_m_id!=ini_m_id)
				sum+=env.getProblem().MMC[ini_m_id][pot_m_id];
		}
		sum*=env.getProblem().getWeightMachineMoveCost();
		return sum;
	}
	
}
